\NeedsTeXFormat{LaTeX2e}
\RequirePackage{l3keys2e}
\ProvidesExplPackage{buffer}{2022/06/10}{v0.1}{buffer}
\RequirePackage{gtl} % process unbalanced tl 

\cs_new_eq:NN \__buffer_expanded:w \tex_expanded:D
\cs_new_eq:NN \__buffer_unexpanded:w \tex_unexpanded:D
\cs_new_eq:NN \__buffer_detokenize:w \tex_detokenize:D
\cs_new_eq:NN \__buffer_scantokens:w \tex_scantokens:D 
\cs_new_eq:NN \__buffer_let_int:w \tex_mathchardef:D 
\cs_new_eq:NN \__buffer_toks:w \tex_toks:D 
\cs_new_eq:NN \__buffer_str_set:Nn \tex_def:D
\cs_new_eq:NN \__buffer_str_set:Nx \tex_edef:D

\cs_new_eq:NN \__buffer_nothing: \prg_do_nothing:
% \cs_new_eq:NN \__buffer_mdfive:n \tex_mdfivesum:D
\cs_new:Npn   \__buffer_mdfive:N #1 { \tex_mdfivesum:D \exp_after:wN {#1} }
\cs_new:Npn   \__buffer_mdfive_v:n #1
  { \tex_mdfivesum:D \exp_after:wN \exp_after:wN \exp_after:wN { \cs:w #1 \cs_end: } }

\tl_new:N \l__buffer_tmpa_tl
\tl_new:N \g__buffer_tmpa_tl
\tl_new:N \l__buffer_tmpa_prop
\scan_new:N \s__buffer_mark
\quark_new:N \q__buffer_stop
\int_new:N \l__buffer_tmpa_int 

%% 不能包含被 let 为 char 的控制序列，如 \bgroup \egroup 等，否则必须被 \BufferVProtect !!
\cs_new:Npn \BufferVProtect #1 { #1 }
\cs_new_eq:NN \BufferELine \scan_stop:
\cs_new_eq:NN \BufferNLine \scan_stop:

\char_set_catcode_active:N \^^M 
\cs_new:Npn \buffer_active_endline: { ^^M }
% 注意这依赖于行的实现方式，由 process_line 决定是否加上这个符号
\tl_new:N \l__buffer_end_of_line_tl
\char_set_catcode_end_line:N \^^M 

\tl_const:Nn \c__buffer_size_tl { 257 }
\tl_const:Nx \c__buffer_active_space_tl 
  { \exp_args:No \exp_not:o { \char_generate:nn { 32 } { 13 } } }

\int_new:N \c@BufferCount % max = 2^31-1 = 21 4748 3647 = 2,147,483,647
\int_new:N \l_buffer_id_int
\tl_new:N \l__buffer_close_environment_tl
\bool_new:N \l__buffer_nested_bool
\tl_new:N \l__buffer_current_line_tl 
\tl_new:N \l__buffer_saved_line_tl
\int_new:N \l__buffer_nest_int 
\clist_new:N \l__buffer_current_save_mode_clist
\iow_new:N \l__buffer_iow
\tl_new:N \l__buffer_set_name_tl
\tl_new:N \l__buffer_push_name_tl
\tl_new:N \l__buffer_tl_name_tl
\tl_new:N \l__buffer_seq_name_tl
\tl_new:N \l__buffer_write_name_tl
\iow_new:N \l__buffer_v_iow
\int_new:N \l__buffer_v_line_int 
\tl_new:N \l__buffer_space_tl % space replace to 
\tl_set_eq:NN \l__buffer_space_tl \c__buffer_active_space_tl

%N buffer中的类别码
% 任何字符都会被完全展开，活动字符必须被 protected 或移除
\cctab_const:Nn \c_buffer_basic_cctab
  {
    \char_set_catcode_other:N \%
    \char_set_catcode_other:N \\
    \char_set_catcode_other:N \#
    \int_step_function:nnnN { `A } { 1 } { `Z } \char_set_catcode_other:n
    \int_step_function:nnnN { `a } { 1 } { `z } \char_set_catcode_other:n
    \char_set_catcode_active:N \  % =\c_space_tl
    \char_set_catcode_active:N \^^M % 
    \char_set_catcode_active:N \^^I 
    \int_set:Nn \tex_endlinechar:D { 13 }
  }

\keys_define:nn { buffer }
  {
    __size .code:n = 
      {
        \bool_lazy_or:nnF \sys_if_engine_ptex_p: \sys_if_engine_uptex_p:
          {
            \tl_set:Nx \c__buffer_size_tl { \int_min:nn { 32767 } { \int_max:nn {#1} { 257 } } }
            \cctab_gset:Nn \c_buffer_basic_cctab
              {
                \cctab_select:N \c_buffer_basic_cctab
                \int_step_function:nnnN { 257 } { \c_one_int } { \c__buffer_size_tl }
                  \char_set_catcode_other:n
                \int_set:Nn \tex_endlinechar:D { 13 }
              }
          }
      } ,
    __start .code:n = \buffer_set_start_eq:n {#1} ,
    __stop  .code:n = \buffer_set_stop_eq:n  {#1} ,
    __process_line .code:n = \buffer_set_process_line_eq:n {#1} ,
    __blank_line   .code:n = \buffer_set_blank_line_eq:n   {#1} ,
    __dispatch_start_stop .code:n = \buffer_set_dispatch_start_stop_eq:n {#1} ,
    __save .code:n = \buffer_set_save_eq:n {#1} ,

    build .choice: ,
    build / macro .meta:n = { __dispatch_start_stop = standard-macro } ,
    macro .meta:n = { __dispatch_start_stop = standard-macro } ,
    build / pair .meta:n = { __dispatch_start_stop = standard-macro } ,
    pair .meta:n = { __dispatch_start_stop = standard-macro } ,
    build / environment .meta:n = { __dispatch_start_stop = standard-environment } ,
    environment .meta:n = { __dispatch_start_stop = standard-environment } ,

    save-mode .meta:n = { __save = { \clist_item:nn {#1} { -1 } } } ,
    multi-save .meta:n = { __save = {#1} } ,

    nestable .bool_set:N = \l__buffer_nested_bool ,
    end-of-line  .tl_set:N = \l__buffer_end_of_line_tl ,
    end-of-line* .tl_set_x:N = \l__buffer_end_of_line_tl ,
    blank .meta:n = { __blank_line = {#1} } ,
    catcode .code:n = 
      {
        \group_begin:
        \char_set_catcode:nn { `\^^M } 
          { \intarray_item:Nn \g__buffer_saved_cctab_intarray { `\^^M + 1 } }
        \int_set:Nn \tex_endlinechar:D 
          { \intarray_item:Nn \g__buffer_saved_cctab_intarray { 257 } }
        #1
        \__buffer_saving_current_catcode:
        \group_end:
      } ,
    current-count .int_set:N = \c@BufferCount ,
    % tab, gobble, space 的替换均依赖于 process_line
    tab  .tl_set:N   = \l__buffer_tab_tl ,
    tab* .tl_set_x:N = \l__buffer_tab_tl ,
    tab-v  .code:n = \tl_set:Nn \l__buffer_tab_tl { \BufferVProtect {#1} } ,
    tab-v* .code:n = \tl_set:Nx \l__buffer_tab_tl { \exp_not:N \BufferVProtect {#1} } ,
    tab-length .meta:n = 
      { tab* = { \prg_replicate:nn {#1} { \exp_not:o \c__buffer_active_space_tl } } } ,
    tab-length .initial:n = 2 ,
    gobble .int_set:N = \l__buffer_gobble_int ,
    space .tl_set:N = \l__buffer_space_tl ,
    prepare-rescan .bool_set:N = \l__buffer_prepare_rescan_bool ,
    % id, 默认名字为最后一个 save mode name (set_name / push_name / write_name)
    id .tl_set:N = \l__buffer_id_tl ,
    id .initial:n = \q_no_value ,

    store-hash .bool_set:N = \l__buffer_mdfive_bool ,

    set .tl_set_x:N = \l__buffer_set_name_tl ,
    set .default:n = buffer ,
    set .initial:n = buffer ,
    push .tl_set_x:N = \l__buffer_push_name_tl ,
    push .default:n = buffer ,
    push .initial:n = buffer ,
    tl .tl_set:N = \l__buffer_tl_name_tl ,
    tl .default:n = \savedbuffertl ,
    tl .initial:n = \savedbuffertl ,
    seq .tl_set:N = \l__buffer_seq_name_tl ,
    seq .default:n = \savedbufferseq ,
    seq .initial:n = \savedbufferseq ,
    write .tl_set_x:N = \l__buffer_write_name_tl ,
    write .initial:n = { \c_sys_jobname_str . buffer } ,
    dir .tl_set_x:N = \l__buffer_dir_tl ,
    dir .initial:n = { ./ } ,

    vdir .tl_set_x:N = \l__buffer_vdir_tl ,
    vdir .initial:n = { ./ } ,
  }
\ProcessKeysPackageOptions { buffer }

\intarray_new:Nn \g__buffer_saved_cctab_intarray { 257 }

% start, stop 标识符可能会不同，因为 catcode=1 的字符可变，使用 id 作为唯一标识
\cs_new_protected:Npn \buffer_start:nnn #1#2#3 % start, stop, id 
  {
    % current id, begin code, end code 均不放在组中
    \tl_set:Nx \l__buffer_current_id_tl { \int_eval:n {#3} }
    \tl_if_exist:cTF { c__buffer_options @ \l__buffer_current_id_tl _clist }
      { 
        \tl_use:c { c__buffer_begin @ \l__buffer_current_id_tl _tl }
        \group_begin:
        \int_gincr:N \c@BufferCount
        \__buffer_saving_current_catcode:
        \__buffer_set_specifier:nn {#1} {#2}
        \keys_set:nv { buffer } { c__buffer_options @ \l__buffer_current_id_tl _clist } 
        \int_set_eq:NN \l__buffer_nest_int \c_one_int
        \char_set_active_eq:NN \  \c_space_tl 
        \int_set:Nn \tex_endlinechar:D { 13 }
        \buffer_do_grab_option:
      }
      { 
        % 不使用正常的结束例程
        \msg_error:nnxxx { buffer } { unknown } 
          { \l__buffer_current_id_tl } { \token_to_str:N #1 } { \token_to_str:N #2 }
      }
  }
\cs_new_protected:Npn \buffer_stop:n #1
  {
    \exp_last_unbraced:Ne \group_end: 
      { 
        \exp_not:o \l__buffer_close_environment_tl 
        #1 % 
        \exp_not:v { c__buffer_end@ \l__buffer_current_id_tl _tl }
      } 
  }
% options, start, stop, begin, end
\cs_new_protected:Npn \buffer_define:nnnnn #1#2#3#4#5 
  {
    \int_incr:N \l_buffer_id_int
    \clist_set:cn { c__buffer_options @ \int_use:N \l_buffer_id_int _clist } {#1}
    \tl_set:cn { c__buffer_begin @ \int_use:N \l_buffer_id_int _tl } {#4}
    \tl_set:cn { c__buffer_end @ \int_use:N \l_buffer_id_int _tl } {#5}
    \tl_if_head_eq_meaning:nNTF {#2} \begin 
      {
        \clist_put_left:cn { c__buffer_options @ \int_use:N \l_buffer_id_int _clist } 
          { build = environment }
        \cs_set_protected:cpx { \use_ii:nn #2 } 
          { 
            \tl_set:Nn \exp_not:N \l__buffer_close_environment_tl { \exp_not:n {#3} }
            \buffer_start:nnn 
              { \exp_not:n {#2} } { \exp_not:n {#3} } { \int_use:N \l_buffer_id_int } 
          } 
        \cs_set_protected:cpn { end \use_ii:nn #3 } { }
      }
      {
        \clist_put_left:cn { c__buffer_options @ \int_use:N \l_buffer_id_int _clist } 
          { build = pair }
        \cs_set_protected:Npx #2
          {
            \buffer_start:nnn
              { \exp_not:N #2 } { \exp_not:N #3 } { \int_use:N \l_buffer_id_int }
          }
        \cs_set_protected:Npn #3 { }
      }
  }
% options, start specifier, stop specifier, begin code, end code 
\NewDocumentCommand \definebufferpair { +O{} m m }
  { 
    \cs_if_exist:NTF #2
      { \msg_error:nnxx { buffer } { exist } { \token_to_str:N #2 } { \token_to_str:N #3 } }
      { \buffer_define:nnnnn {#1} #2 #3 }
  }
% options, environment name, begin code, end code 
\NewDocumentCommand \definebufferenvironment { +O{} >{ \TrimSpaces } m }
  { 
    \cs_if_exist:cTF {#2}
      { \msg_error:nnxx { buffer } { exist } { \token_to_str:N \begin {#2} } { \token_to_str:N \end {#2} } }
      { \buffer_define:nnnnn {#1} { \begin{#2} } { \end{#2} } } 
  }
\msg_new:nnn { buffer } { exist }
  { The~buffer~command~starts~with~`#2'~and~ends~with~`#2'~already~exists. }
\msg_new:nnn { buffer } { unknown }
  { The~buffer~command~(id=#1)~starts~with~`#2'~and~ends~with~`#3'~is~unknown. }
\msg_new:nnn { buffer } { unbalanced }
  { The~buffer~command~(id=#1)~starts~with~`#2'~and~ends~with~`#3'~is~unbalanced. }

\definebufferpair \buffer_open: \buffer_close: { } { }
\definebufferpair \startbuffer \stopbuffer { } { }
\definebufferenvironment { buffer } { } { }


\cs_new_nopar:Npn \buffer_init: 
  {
    \cs_set:Npx \BufferVProtect ##1 { \c_space_tl }
    \cs_set_eq:NN \BufferELine \scan_stop:
    \cs_set_eq:NN \BufferNLine \scan_stop:
  }
\cs_new:Npn \__buffer_save_outer: { } % 它用于在组外保存内容，会被完全展开

%region start 
\cs_new_protected:Npn \buffer_set_start:nn #1
  { \exp_args:Nc \cs_set_protected:Npn { __buffer_start @/~ #1 : } }
\cs_new_protected:Npn \buffer_set_start_eq:n #1
  { \cs_set_eq:Nc \buffer_do_start: { __buffer_start @/~ #1 : } }
\cs_new_protected:Npn \buffer_set_start_alias:nn #1#2 
  { \cs_set_eq:cc { __buffer_start @/~ #1 : } { __buffer_start @/~ #2 : } }
\buffer_set_start:nn { standard }
  {
    \buffer_init:
    \cctab_select:N \c_buffer_basic_cctab
    \buffer_do_process_line:n 
  }
\buffer_set_start_eq:n { standard }
%endregion

%region stop 
\cs_new_protected:Npn \buffer_set_stop:nn #1
  { \exp_args:Nc \cs_set_protected:Npn { __buffer_stop @/~ #1 : } }
\cs_new_protected:Npn \buffer_set_stop_eq:n #1
  { \cs_set_eq:Nc \buffer_do_stop: { __buffer_stop @/~ #1 : } }
\cs_new_protected:Npn \buffer_set_stop_alias:nn #1#2 
  { \cs_set_eq:cc { __buffer_stop @/~ #1 : } { __buffer_stop @/~ #2 : } }
\buffer_set_stop:nn { standard }
  {
    \buffer_stop:n { \__buffer_save_outer: }
  }
\buffer_set_stop_eq:n { standard }
%endregion 

%region process line 
\char_set_catcode_active:N \^^M 
\cs_new_protected:Npn \buffer_set_process_line:nn #1 %
  { \exp_args:Nc \cs_set_protected:Npn { __buffer_process_line @/~ #1 :n } ##1 ^^M } %
\char_set_catcode_end_line:N \^^M 
\cs_new_protected:Npn \buffer_set_process_line_eq:n #1
  { \cs_set_eq:Nc \buffer_do_process_line:n { __buffer_process_line @/~ #1 :n } }
\cs_new_protected:Npn \buffer_set_process_line_alias:nn #1#2 
  { \cs_set_eq:cc { __buffer_process_line @/~ #1 :n } { __buffer_process_line @/~ #2 :n } }
% 可嵌套，添加 end_of_line 
\buffer_set_process_line:nn { standard-nest }
  {
    \__buffer_save_line:n {#1}
    \__buffer_if_blank:nTF { \l__buffer_saved_line_tl }
      { 
        \buffer_do_blank_line: 
        \buffer_do_process_line:n 
      }
      {
        \buffer_do_dispatch_start_stop:onnn \l__buffer_saved_line_tl
          { 
            \int_incr:N \l__buffer_nest_int 
            \__buffer_replace_space:
            \buffer_do_save:e { \exp_not:o \l__buffer_saved_line_tl \exp_not:o \l__buffer_end_of_line_tl }
            \buffer_do_process_line:n 
          }
          {
            \int_decr:N \l__buffer_nest_int
            \if_int_compare:w \l__buffer_nest_int > \c_zero_int % 正确 nest 
              \__buffer_replace_space:
              \buffer_do_save:e { \exp_not:o \l__buffer_saved_line_tl \exp_not:o \l__buffer_end_of_line_tl }
              \exp_after:wN \buffer_do_process_line:n 
            \else:
              \exp_after:wN \buffer_do_stop:
            \fi:
          }
          { 
            \__buffer_replace_space:
            \buffer_do_save:e { \exp_not:o \l__buffer_saved_line_tl \exp_not:o \l__buffer_end_of_line_tl }
            \buffer_do_process_line:n 
          }
      }
  }
% 不嵌套，添加 end_of_line
\buffer_set_process_line:nn { standard-not-nest }
  {
    \__buffer_save_line:n {#1}
    \__buffer_if_blank:nTF { \l__buffer_saved_line_tl }
      {
        \buffer_do_blank_line:
        \buffer_do_process_line:n 
      }
      {
        \buffer_do_dispatch_start_stop:onnn \l__buffer_saved_line_tl 
          { \use_ii:nn } { \use_i:nn } { \use_ii:nn }
            { \int_decr:N \l__buffer_nest_int \buffer_do_stop: }
            { 
              \__buffer_replace_space:
              \buffer_do_save:e 
                { \exp_not:o \l__buffer_saved_line_tl \exp_not:o \l__buffer_end_of_line_tl }
              \buffer_do_process_line:n 
            }
      }
  }
\buffer_set_process_line:nn { standard }
  {
    \bool_if:NTF \l__buffer_nested_bool
      { \use:c { __buffer_process_line @/~ standard-nest :n } }
      { \use:c { __buffer_process_line @/~ standard-not-nest :n } }
        {#1}
  }
\group_begin:
\char_set_catcode_active:n { 9 }
\cs_new_protected:Npn \__buffer_save_line:n #1 % gobble int <= 9
  {
    \__buffer_str_set:Nx \l__buffer_saved_line_tl
      {
        \__buffer_gobble:nw \l__buffer_gobble_int #1
          \__buffer_nothing: \__buffer_nothing: \__buffer_nothing:
          \__buffer_nothing: \__buffer_nothing: \__buffer_nothing:
          \__buffer_nothing: \__buffer_nothing: \__buffer_nothing:
          \__buffer_nothing: \s_stop
      }
    \exp_args:NNno \tl_replace_all:Nnn \l__buffer_saved_line_tl 
      { ^^I } { \l__buffer_tab_tl } 
  }
% \cs_new_protected:Npn \__buffer_save_line:n #1 % any integer
%   {
%     \buffer_exp_args:NNd \__buffer_str_set:Nn \l__buffer_saved_line_tl
%       {
%         \buffer_use_none_num:nw 
%           { \int_min:nn { \l__buffer_gobble_int } { \tl_count:n {#1} } }
%           #1
%       }
%     \exp_args:NNno \tl_replace_all:Nnn \l__buffer_saved_line_tl 
%       { ^^I } { \l__buffer_tab_tl } 
%   }
\group_end:
\cs_new_protected:Npn \__buffer_replace_space:
  {
    \reverse_if:N \if_meaning:w \l__buffer_space_tl \c__buffer_active_space_tl
      \exp_args:NNoo \tl_replace_all:Nnn \l__buffer_saved_line_tl 
        \c__buffer_active_space_tl \l__buffer_space_tl
    \fi:
  }
\buffer_set_process_line_eq:n { standard-nest }
%endregion

%region blank line 
\cs_new_protected:Npn \buffer_set_blank_line:nn #1
  { \exp_args:Nc \cs_set_protected:Npn { __buffer_blank_line @/~ #1 : } }
\cs_new_protected:Npn \buffer_set_blank_line_eq:n #1
  { \cs_set_eq:Nc \buffer_do_blank_line: { __buffer_blank_line @/~ #1 : } }
\cs_new_protected:Npn \buffer_set_blank_line_alias:nn #1#2 
  { \cs_set_eq:cc { __buffer_blank_line @/~ #1 : } { __buffer_blank_line @/~ #2 : } }
\buffer_set_blank_line:nn { newlinechar }
  { \buffer_do_save:o { \iow_newline: } }
\buffer_set_blank_line:nn { active-endline }
  { \buffer_do_save:o { \buffer_active_endline: } }
\buffer_set_blank_line:nn { space }
  { \buffer_do_save:o { \c_space_tl } }
\buffer_set_blank_line:nn { newpara }
  { \buffer_do_save:n { \BufferVProtect {\par \null \par} } }
\buffer_set_blank_line:nn { par }
  { \buffer_do_save:n { \par } }
\buffer_set_blank_line:nn { nline }
  { \buffer_do_save:n { \BufferNLine } }
\buffer_set_blank_line_eq:n { newlinechar }
%endregion 

%region dispatch start stop 
\cs_new_protected:Npn \buffer_set_dispatch_start_stop:nn #1
  { \exp_args:Nc \cs_set_protected:Npn { __buffer_dispatch_start_stop @/~ #1 :nnnn } ##1##2##3##4 }
\cs_new_protected:Npn \buffer_set_dispatch_start_stop_eq:n #1
  { \cs_set_eq:Nc \buffer_do_dispatch_start_stop:nnnn { __buffer_dispatch_start_stop @/~ #1 :nnnn } }
\cs_new_protected:Npn \buffer_set_dispatch_start_stop_alias:nn #1#2 
  { \cs_set_eq:cc { __buffer_dispatch_start_stop @/~ #1 :nnnn } { __buffer_dispatch_start_stop @/~ #2 :nnnn } }
\buffer_set_dispatch_start_stop:nn { standard-macro }
  {
    \__buffer_str_set:Nx \l__buffer_current_line_tl 
      { \__buffer_remove_head_space:n {#1} }
    \__buffer_str_if_startswith:ooTF 
      \l__buffer_current_line_tl 
      \l__buffer_start_specifier_tl
      {#2}
      {
        \__buffer_str_if_startswith:ooTF 
          \l__buffer_current_line_tl
          \l__buffer_stop_specifier_tl
          {#3}
          {#4}
      }
  }
\buffer_set_dispatch_start_stop:nn { standard-environment }
  {
    \__buffer_str_set:Nx \l__buffer_current_line_tl 
      { \__buffer_remove_head_space:n {#1} }
    % 首先判断是否 startswith \begin
    \__buffer_str_if_startswith:eeTF 
      { \exp_not:o \l__buffer_current_line_tl }
      { 
        \if_int_compare:w \tex_escapechar:D > \c_zero_int 
          \char_generate:nn { \tex_escapechar:D } { 12 } 
        \fi: 
        \tl_to_str:n { begin } 
      }
      {
        % 如果是，则移除第一个begin-group字符前的所有字符
        \__buffer_str_set:Nx \l__buffer_current_line_tl 
          {
            \if_int_compare:w \tex_escapechar:D > \c_zero_int
              \char_generate:nn { \tex_escapechar:D } { 12 } 
            \fi:
            \tl_to_str:n { begin }
            \exp_args:NNo \exp_args:No \__buffer_remove_group_leading_chars:n 
              { \exp_after:wN \use_none:nnnnn \l__buffer_current_line_tl }
          }
        % 判断是否为 start 标记
        \__buffer_str_if_startswith:ooTF 
          { \l__buffer_current_line_tl } { \l__buffer_start_specifier_tl }
          {#2}
          {#4}
      }
      {
        % 如果 startswith \end 
        \__buffer_str_if_startswith:eeTF 
          { \exp_not:o \l__buffer_current_line_tl }
          {
            \if_int_compare:w \tex_escapechar:D > \c_zero_int 
              \char_generate:nn { \tex_escapechar:D } { 12 } 
            \fi: 
            \tl_to_str:n { end } 
          }
          {
            % 如果是，则移除第一个begin-group字符前的所有字符
            \__buffer_str_set:Nx \l__buffer_current_line_tl 
              {
                \if_int_compare:w \tex_escapechar:D > \c_zero_int
                  \char_generate:nn { \tex_escapechar:D } { 12 } 
                \fi:
                \tl_to_str:n { end }
                \exp_args:NNo \exp_args:No \__buffer_remove_group_leading_chars:n 
                  { \exp_after:wN \use_none:nnn \l__buffer_current_line_tl }
              }
            % 判断是否为 stop 标记
            \__buffer_str_if_startswith:ooTF 
              { \l__buffer_current_line_tl } { \l__buffer_stop_specifier_tl }
              {#3}
              {#4}
          }
          {#4}
      }
  }
\cs_generate_variant:Nn \buffer_do_dispatch_start_stop:nnnn { o }
\cs_new:Npn \__buffer_remove_group_leading_chars:n #1
  {
    \__buffer_remove_group_leading_chars_aux:N #1
      \__buffer_nothing:
  }
\cs_new:Npn \__buffer_remove_group_leading_chars_aux:N #1
  {
    \int_compare:nNnTF 
      { \intarray_item:Nn \g__buffer_saved_cctab_intarray { `#1 + 1 } } = { 1 }
      { \__buffer_unexpanded_by_nothing:w #1 }
      { \__buffer_remove_group_leading_chars_aux:N }
  }
\buffer_set_dispatch_start_stop_eq:n { standard-macro }
%endregion

%region save, do_save is very different!
% 为了相同的模式不重复保存，需要检查是否已经存在
% 但 alias 也要考虑进去，使用一个 prop 保存真实的名字
\prop_new:N \l__buffer_save_mode_prop
\cs_new_protected:Npn \buffer_set_save:nn #1
  { 
    \prop_put:Nxx \l__buffer_save_mode_prop {#1} {#1}
    \exp_args:Nc \cs_set_protected:Npn { __buffer_save @/~ #1 :n } ##1 
  }
\cs_new_protected:Npn \buffer_set_save_eq:n #1
  {
    \clist_set:Nx \l__buffer_tmpa_clist {#1}
    \clist_clear:N \l__buffer_current_save_mode_clist
    \clist_map_inline:Nn \l__buffer_tmpa_clist
      {
        \prop_get:NnNTF \l__buffer_save_mode_prop {##1} \l__buffer_tmpa_tl
          {
            \clist_if_in:NoF \l__buffer_current_save_mode_clist \l__buffer_tmpa_tl
              { \clist_put_right:Nn \l__buffer_current_save_mode_clist {##1} }
          }
          { \msg_warning:nnn { buffer } { save-mode-unknown } {##1} }
      }
    \cs_set:Npn \__buffer_tmp:w ##1
      { \exp_not:c { __buffer_save @/~ ##1 :n } {####1} }
    \cs_set_protected:Npx \buffer_do_save:n ##1
      { \clist_map_function:NN \l__buffer_current_save_mode_clist \__buffer_tmp:w }
  }
\cs_new_protected:Npn \buffer_set_save_alias:nn #1#2 
  { \prop_put:Nxx \l__buffer_save_mode_prop {#1} {#2} }
\cs_new_protected:Npn \buffer_define_get:nn #1
  { \cs_set_protected:cpn { __buffer_get_by_ #1 :Nnn } ##1##2##3 } % res, name, extra 
\cs_new:Npn \__buffer_tl_item:n #1 {#1}
% 仿照 seq，在内部使用 \__buffer_tl_item:n，这样能更快的 get 
\buffer_set_save:nn { set }
  {
    \tl_put_right:cn { l__buffer_saved@ \l__buffer_set_name_tl _tl } 
      { \__buffer_tl_item:n {#1} }
  }
\buffer_define_get:nn { set } 
  { 
    \cs_set_eq:NN \__buffer_tmp:w \__buffer_tl_item:n 
    \cs_set_eq:NN \__buffer_tl_item:n \__buffer_unexpanded:w 
    \tl_set:Nx #1 { \tl_use:c { l__buffer_saved@ #2 _tl } }
    \cs_set_eq:NN \__buffer_tl_item:n \__buffer_tmp:w 
  }
\tl_put_right:Nn \buffer_init: 
  {
    % 只有设置了当前模式时才清除，否则不清除
    \clist_if_in:NnT \l__buffer_current_save_mode_clist { set }
      {
        \tl_clear:c { l__buffer_saved@ \l__buffer_set_name_tl _tl }
        \tl_put_right:Nn \__buffer_save_outer:
          { 
            \__buffer_set_self:n { l__buffer_saved@ \l__buffer_set_name_tl _tl }
          }
      }
  }
\buffer_set_save:nn { push }
  {
    % not really push 
    \seq_put_right:cn { l__buffer_saved@ \l__buffer_push_name_tl _seq } {#1}
  }
\buffer_define_get:nn { push }
  { 
    \quark_if_no_value:nTF {#3} 
      {
        % 使用相同的 trick
        \cs_set_eq:NN \__buffer_tmp:w \__seq_item:n 
        \cs_set_eq:NN \__seq_item:n \__buffer_unexpanded:w 
        \tl_set:Nx #1 { \exp_last_unbraced:Nv \use_none:n { l__buffer_saved@ #2 _seq } }
        \cs_set_eq:NN \__seq_item:n \__buffer_tmp:w 
      }
      { \tl_set:Nx #1 { \seq_item:cn { l__buffer_saved@ #2 _seq } {#3} } }
  }
\tl_put_right:Nn \buffer_init: 
  {
    \clist_if_in:NnT \l__buffer_current_save_mode_clist { push }
      {
        \seq_clear:c { l__buffer_saved@ \l__buffer_push_name_tl _seq }
        \tl_put_right:Nn \__buffer_save_outer:
          { 
            \__buffer_set_self:n { l__buffer_saved@ \l__buffer_push_name_tl _seq }
          }
      }
  }
\buffer_set_save:nn { tl }
  {
    \exp_after:wN \tl_put_right:Nn \l__buffer_tl_name_tl {#1}
  }
\tl_put_right:Nn \buffer_init: 
  {
    \clist_if_in:NnT \l__buffer_current_save_mode_clist { tl }
      {
        \exp_after:wN \tl_clear:N \l__buffer_tl_name_tl
        \exp_args:NNo \tl_put_right:Nn \__buffer_save_outer:
          { 
            \exp_after:wN \__buffer_set_self:N \l__buffer_tl_name_tl
          }
      }
  }
\buffer_set_save:nn { seq }
  {
    \exp_after:wN \seq_put_right:Nn \l__buffer_seq_name_tl {#1}
  }
\tl_put_right:Nn \buffer_init: 
  {
    \clist_if_in:NnT \l__buffer_current_save_mode_clist { seq }
      {
        \exp_after:wN \seq_clear:N \l__buffer_seq_name_tl
        \exp_args:NNo \tl_put_right:Nn \__buffer_save_outer:
          { 
            \exp_after:wN \__buffer_set_self:N \l__buffer_seq_name_tl
          }
      }
  }
\buffer_set_save:nn { write }
  {
    \iow_now:Nn \l__buffer_iow {#1}
  }
\buffer_define_get:nn { write } { \file_get:nnN {#2} {#3} #1 }
\tl_put_right:Nn \buffer_init:
  {
    \clist_if_in:NnT \l__buffer_current_save_mode_clist { write }
      {
        \exp_args:No \__buffer_str_if_endswith:nnTF \l__buffer_dir_tl { / }
          { \iow_open:Nn \l__buffer_iow { \l__buffer_dir_tl \l__buffer_write_name_tl } }
          { \iow_open:Nn \l__buffer_iow { \l__buffer_dir_tl / \l__buffer_write_name_tl } }
        \tl_put_right:Nn \__buffer_save_outer: { \iow_close:N \l__buffer_iow }
      }
  }
\buffer_set_save_eq:n { set }
\cs_generate_variant:Nn \buffer_do_save:n { o, e }
\cs_new:Npn \__buffer_set_self:n #1 { \tl_set:cn {#1} { \exp_not:v {#1} } }
\cs_new:Npn \__buffer_set_self:N #1 { \tl_set:Nn \exp_not:N #1 { \exp_not:o {#1} } }
\msg_new:nnn { buffer } { save-mode-unknown }
  { The~buffer~save~mode~`#1'~is~unknown. }
\tl_put_right:Nn \buffer_init:
  {
    \prop_clear:N \l__buffer_tmpa_prop
    \quark_if_no_value:NTF \l__buffer_id_tl
      {
        \clist_map_inline:Nn \l__buffer_current_save_mode_clist
          {
            \clist_if_in:nnT { set, push, write } {#1}
              { 
                \tl_set:Nx \l__buffer_id_tl { \tl_use:c { l__buffer_#1_name_tl } } 
                \prop_put:Nno \l__buffer_tmpa_prop {#1} \l__buffer_id_tl
              }
          }
      }
      {
        \clist_map_inline:Nn \l__buffer_current_save_mode_clist
          {
            \clist_if_in:nnT { set, push, write } {#1}
              { 
                \prop_put:Nnx \l__buffer_tmpa_prop {#1} 
                  { \exp_not:v { l__buffer_#1_name_tl } }
              }
          }
      }
    \quark_if_no_value:NT \l__buffer_id_tl
      { 
        \tl_set:Nx \l__buffer_id_tl { \int_use:N \c@BufferCount } 
      }
    \prop_put:Nnx \l__buffer_tmpa_prop { id } { \int_use:N \c@BufferCount }
    \tl_put_right:Nx \__buffer_save_outer:
      { 
        \tl_set:Nn \exp_not:N \exp_not:N 
        \exp_not:c { l__buffer_saved_id_to_name@ \l__buffer_id_tl _prop }
          { \exp_not:N \exp_not:n { \exp_not:o \l__buffer_tmpa_prop } }
      }
    \bool_if:NT \l__buffer_prepare_rescan_bool
      {
        \cs_if_exist:cF { g__buffer_saved_cctab@ \l__buffer_id_tl _intarray }
          { \intarray_new:cn { g__buffer_saved_cctab@ \l__buffer_id_tl _intarray } { 257 } }
        \int_step_inline:nn { 257 }
          {
            \intarray_gset:cnn { g__buffer_saved_cctab@ \l__buffer_id_tl _intarray }
              {#1} { \intarray_item:Nn \g__buffer_saved_cctab_intarray {#1} }
          }
      }
  }
%endregion 

%region getbuffer
\str_const:Nx \c__buffer_lbrace_str { ([ \cs_to_str:N \{ } % ])}
\str_const:Nx \c__buffer_rbrace_str { ]) \cs_to_str:N \} }
\str_const:Nx \c__buffer_punct_str { \cs_to_str:N \~ !@ \c_hash_str \c_dollar_str \c_percent_str ^&_|?:;"',. }
\str_const:Nx \c__buffer_operator_str { +-/*<>= }
\str_const:Nx \c__buffer_digit_str { 1234567890 }
\str_const:Nx \c__buffer_Alpha_str { ABCDEFGHIJKLMNOPQRSTUVWXYZ }
\str_const:Nx \c__buffer_alpha_str { abcdefghijklmnopqrstuvwxyz }
\tl_new:N \l__buffer_rescan_catcode_tl
\tl_new:N \g__buffer_get_tl
\bool_new:N \l__buffer_get_rescan_bool
\bool_set_true:N \l__buffer_get_rescan_bool
\tl_new:N \l__buffer_get_item_tl
\tl_set_eq:NN \l__buffer_get_item_tl \q_no_value

\keys_define:nn { buffer/_get }
  {
    mode .tl_set_x:N = \l__buffer_get_type_tl ,
    mode .initial:n = set ,
    type .meta:n = { mode = {#1} } ,
    item .tl_set:N = \l__buffer_get_item_tl ,
    dir .tl_set:N = \l__buffer_get_dir_tl ,
    dir .initial:n = { ./ } ,
    debug .bool_set:N = \l__buffer_get_debug_bool ,
    rescan .choice: ,
    rescan / former .code:n = \tl_set_eq:NN \l__buffer_rescan_catcode_tl \q_no_value ,
    rescan / current .code:n = \tl_clear:N \l__buffer_rescan_catcode_tl ,
    rescan / code .code:n = \tl_set:Nn \l__buffer_rescan_catcode_tl 
      { \cctab_select:N \c_code_cctab } ,
    rescan / document .code:n = \tl_set:Nn \l__buffer_rescan_catcode_tl 
      { \cctab_select:N \c_document_cctab } ,
    rescan / other .code:n = \tl_set:Nn \l__buffer_rescan_catcode_tl
      { \cctab_select:N \c_other_cctab } ,
    rescan / true  .code:n = \bool_set_true:N  \l__buffer_get_rescan_bool ,
    rescan / false .code:n = \bool_set_false:N \l__buffer_get_rescan_bool ,
    rescan .default:n = true ,
    catcode .tl_set:N = \l__buffer_get_catcode_tl ,
    raw .bool_set:N = \l__buffer_get_raw_bool ,

    eline .tl_gset:N = \BufferELine ,
    eline*   .code:n = \cs_gset_eq:NN \BufferELine #1 ,
    nline .tl_gset:N = \BufferNLine ,
    nline*   .code:n = \cs_gset_eq:NN \BufferNLine #1 ,
  }
\cs_new_protected:Npn \__buffer_get:n #1
  {
    \cs_if_exist:cTF { __buffer_get_by_ \l__buffer_get_type_tl :Nnn }
      {
        \prop_get:coNTF { l__buffer_saved_id_to_name@ #1 _prop } \l__buffer_get_type_tl
          \l__buffer_tmpa_tl 
          {
            \bool_if:NTF \l__buffer_get_raw_bool
              { \__buffer_get_raw:n {#1} }
              { \__buffer_get_rescan:n {#1} }
          }
          {
            \tl_set:Nx \g__buffer_get_tl
              { \msg_error:nnnn { buffer } { id-mode-unknown } {#1} { \l__buffer_get_type_tl } }
          }
      }
      { 
        \tl_set:Nx \g__buffer_get_tl 
          { \msg_warning:nnn { buffer } { mode-get-unsupported } { \l__buffer_get_type_tl } } 
      }

    \bool_if:NT \l__buffer_get_debug_bool
      {
        \iow_log:x 
          { 
            ((buffer)getting~from~buffer: \iow_newline: 
              \c_space_tl type=[\l__buffer_get_type_tl]; \iow_newline:
              \c_space_tl raw=[\bool_if:NTF \l__buffer_get_raw_bool { True } { False }]; \iow_newline:
              \c_space_tl id=[#1]; \iow_newline:
              \c_space_tl rescan=[
                \quark_if_no_value:NTF \l__buffer_rescan_catcode_tl { NULL }
                  \l__buffer_rescan_catcode_tl ] ; \iow_newline:
            )
          }
      }
  }
\cs_new_protected:Npn \__buffer_get_rescan:n #1
  {
    \bool_if:NTF \l__buffer_get_rescan_bool
      {
        \quark_if_no_value:NTF \l__buffer_rescan_catcode_tl
          {
            \cs_if_exist:cTF { g__buffer_saved_cctab@ #1 _intarray } 
              { 
                \tl_put_left:Nn \l__buffer_get_catcode_tl
                  {
                    \int_step_inline:nnnn { 0 } { 1 } { 255 }
                      {
                        \char_set_catcode:nn { ##1 }
                          { \intarray_item:cn { g__buffer_saved_cctab@ #1 _intarray } { ##1 + 1 } }
                      }
                    \int_set:Nn \tex_endlinechar:D 
                      { \intarray_item:cn { g__buffer_saved_cctab@ #1 _intarray } { 257 } }
                  }
              }
              {
                \msg_warning:nnn { buffer } { prepare-rescan } {#1}
                \tl_put_left:Nn \l__buffer_get_catcode_tl
                  { \cctab_select:N \c_buffer_basic_cctab }
              }
          }
          {
            \tl_put_left:No \l__buffer_get_catcode_tl
              \l__buffer_rescan_catcode_tl
          }
      }
      {
        \tl_put_left:Nn \l__buffer_get_catcode_tl
          { \cctab_select:N \c_buffer_basic_cctab }
      }
    \tl_put_left:Nx \l__buffer_get_catcode_tl
      { \int_set:Nn \tex_newlinechar:D { \int_use:N \tex_newlinechar:D } }
    \str_if_eq:eeTF \l__buffer_get_type_tl { write }
      {
        \__buffer_get_by_write:Nnn \g__buffer_get_tl
          { \l__buffer_get_dir_tl \l__buffer_tmpa_tl } 
          { \l__buffer_get_catcode_tl }
      }
      {
        \use:e 
          {
            \exp_not:c { __buffer_get_by_ \l__buffer_get_type_tl :Nnn } 
              \exp_not:N \g__buffer_get_tl
              { \exp_not:o \l__buffer_tmpa_tl }
              { \exp_not:o \l__buffer_get_item_tl }
          }
        \tl_set_rescan:Nno \g__buffer_get_tl { \l__buffer_get_catcode_tl }
          \g__buffer_get_tl
      }
  }
\cs_new_protected:Npn \__buffer_get_raw:n #1
  {
    \str_if_eq:eeTF \l__buffer_get_type_tl { write }
      { 
        \file_get:nnN { \l__buffer_get_dir_tl \l__buffer_tmpa_tl } 
          { \l__buffer_get_catcode_tl } \g__buffer_get_tl 
      }
      {
        \use:e 
          {
            \exp_not:c { __buffer_get_by_ \l__buffer_get_type_tl :Nnn } 
              \exp_not:N \g__buffer_get_tl
              { \exp_not:o \l__buffer_tmpa_tl }
              { \exp_not:o \l__buffer_get_item_tl }
          }
      }
  }
\NewDocumentCommand \getbuffer { O{} >{ \TrimSpaces } m }
  {
    \group_begin:
      \keys_set:nn { buffer/_get } {#1}
      \__buffer_get:n {#2}
      \tl_gset:No \g__buffer_get_tl { \g__buffer_get_tl }
    \group_end:
   \g__buffer_get_tl
  }
\NewDocumentCommand \setfrombuffer { m O{} >{ \TrimSpaces } m }
  {
    \group_begin:
      \keys_set:nn { buffer/_get } {#2}
      \__buffer_get:n {#3}
      \tl_gset:No \g__buffer_get_tl { \g__buffer_get_tl }
    \group_end:
    \tl_set_eq:NN #1 \g__buffer_get_tl
  }
\msg_new:nnn { buffer } { id-unknown }
  { The~buffer~id~`#1'~is~unknown. }
\msg_new:nnn { buffer } { id-mode-unknown }
  { The~buffer~id~`#1'~has~not~be~saved~in~mode~`#2'. }
\msg_new:nnn { buffer } { mode-get-unsupported }
  { The~buffer~mode~`#1'~is~unknown,~or~get~from~buffer~is~unsupported. }
\msg_new:nnn { buffer } { prepare-rescan }
  { You~need~set~`prepare-rescan=true'~in~buffer~(id=#1),~or~you~cannot~set~`rescan=former'. }
%endregion

\cs_new_protected:Npn \__buffer_saving_current_catcode:
  {
    \int_step_inline:nnnn { 0 } { 1 } { 255 }
      { 
        \intarray_gset:Nnn \g__buffer_saved_cctab_intarray { ##1 + 1 } 
          { \char_value_catcode:n {##1} }
      }
    \intarray_gset:Nnn \g__buffer_saved_cctab_intarray { 257 } { \tex_endlinechar:D }
  }

\cs_new:Npn \buffer_do_grab_option: 
  { 
    % 首先设置 ^^M=active，则仅会检测当前行的可选项 []
    \char_set_catcode_active:N \^^M 
    \__buffer_start_option_grab_o:w 
  }
\char_set_catcode_active:N \^^M 
\NewDocumentCommand \__buffer_start_option_grab_o:w { +o }
  {
    \tl_if_novalue:nF {#1}
      {
        \tl_set:Nn \l__buffer_options_clist {#1}
        \tl_replace_all:Nnn \l__buffer_options_clist { ^^M } { ~ }
        \keys_set:no { buffer } \l__buffer_options_clist
      }
    \peek_charcode:NTF ^^M 
      { \exp_after:wN \buffer_do_start: \use_none:n } % remove ^^M 
      { \buffer_do_start: }
  }
\char_set_catcode_end_line:N \^^M 
\cs_new_protected:Npn \__buffer_set_specifier:nn #1#2
  {
    \str_set:Nx \l__buffer_start_specifier_tl { \token_to_str:N #1 }
    \str_set:Nx \l__buffer_stop_specifier_tl  { \token_to_str:N #2 }
  }


\cs_new:Npn \__buffer_if_empty:nTF #1
  { 
    \if_meaning:w \scan_stop: #1 \scan_stop: 
      \exp_after:wN \use_i:nn 
    \else: 
      \exp_after:wN \use_ii:nn 
    \fi: 
  }
\cs_new:Npn \__buffer_if_blank:nTF #1
  { 
    \if:w \scan_stop: \exp_after:wN \use_none:n 
      \__buffer_detokenize:w \__buffer_expanded:w {{#1}} ? \scan_stop: 
      \exp_after:wN \use_i:nn 
    \else: 
      \exp_after:wN \use_ii:nn 
    \fi: 
  }
\cs_new_protected:Npn \__buffer_remove_head_space:N #1
  {
    \cs_set_nopar:Nx #1
      {
        \exp_after:wN \__buffer_remove_head_space_aux:N \__buffer_expanded:w {#1}
          \__buffer_nothing: \__buffer_nothing: \q_stop
      }
  }
\cs_new:Npn \__buffer_remove_head_space:n #1
  {
    \exp_after:wN \__buffer_remove_head_space_aux:N \__buffer_expanded:w {#1}
      \__buffer_nothing: \__buffer_nothing: \q_stop
  }
\cs_new:Npn \__buffer_remove_head_space_aux:N #1
  {
    \__buffer_if_blank:nTF {#1}
      { \__buffer_remove_head_space_aux:N    }
      { \__buffer_remove_head_space_aux:w #1 }
  }
\cs_new:Npn \__buffer_remove_head_space_aux:w #1 #2 \__buffer_nothing: #3 \q_stop
  { \__buffer_unexpanded:w { #1 #2 } }
\cs_new_protected:Npx \__buffer_remove_active_spaces:N #1
  {
    \tl_replace_all:Nnn #1 { \exp_not:o \c__buffer_active_space_tl } { }
  }
\cs_new:Npn \__buffer_unexpanded_by_nothing:w #1 \__buffer_nothing:
  { \__buffer_unexpanded:w {#1} }
\cs_new:Npn \__buffer_str_mapthread_function:nnN #1#2#3
  {
    \__buffer_str_mapthread_function_aux:Nnwn #3 
      #1 { ? \prg_break: } { } \s__buffer_mark 
      #2 { ? \prg_break: } { }
    \prg_break_point:
  }
\cs_new:Npn \__buffer_str_mapthread_function_aux:Nnwn #1#2#3 \s__buffer_mark #4
  {
    \use_none:n #2 \use_none:n #4
    #1 {#2} {#4}
    \__buffer_str_mapthread_function_aux:Nnwn #1#3 \s__buffer_mark
  }
\cs_generate_variant:Nn \__buffer_str_mapthread_function:nnN { oo }
\cs_new:Npn \__buffer_str_if_startswith:nnTF #1#2
  {
    \cs_set:Npn \__buffer_tmp:w ##1 \q_mark #2 ##2 \s_stop ##3 \q_stop 
      { \__buffer_if_empty:nTF {##3} { } \__buffer_turn_true:w }
    \__buffer_tmp:w \q_mark #1 \s_stop \q_mark #2 \s_stop \q_stop 
    \if_false: \exp_after:wN \use_i:nn \else: \exp_after:wN \use_ii:nn \fi:
  }
\cs_generate_variant:Nn \__buffer_str_if_startswith:nnTF { oo, ee }
\cs_new:Npn \__buffer_str_if_endswith:nnTF #1#2
  {
    \cs_set:Npn \__buffer_tmp:w ##1 #2 \q_mark ##2 \q_stop
      { \__buffer_if_empty:nTF {##2} { } \__buffer_turn_true:w }
    \__buffer_tmp:w #1 \q_mark #2 \q_mark \q_stop 
    \if_false: \exp_after:wN \use_i:nn \else: \exp_after:wN \use_ii:nn \fi:
  }
\cs_generate_variant:Nn \__buffer_str_if_endswith:nnTF { oo }
\cs_new_protected:Npn \__buffer_turn_true:w \if_false: { \if_true: }
% copy from ltxcmds.sty, rewritten in LaTeX3, needs two expansion steps 
\cs_new:Npn \buffer_use_none_num:nw #1
  {
    \exp:w \cs:w exp_end:
    \exp_after:wN \__buffer_use_none_num_aux:n 
    \tex_romannumeral:D \int_eval:n {#1} 000 { m \cs_end: }
  }
\cs_new:Npn \__buffer_use_none_num_aux:n #1
  { \cs:w __buffer_use_none_aux_:_ #1 \__buffer_use_none_num_aux:n }
\cs_new:Npn \__buffer_use_none_aux_:_m #1 { \cs_end: }
\cs_new:Npn \buffer_exp_args:NNd #1#2#3
  {
    \exp_after:wN #1 \exp_after:wN #2 \exp_after:wN
    { 
      \exp:w \exp_end_continue_f:w 
      \exp_after:wN \exp_after:wN \exp_after:wN \exp_stop_f: #3 
    }
  }
\cs_new:Npn \__buffer_gobble:nw #1
  {
    % ignore explicit spaces 
    \if_case:w \int_eval:w #1 \scan_stop: \exp_stop_f: 
           \__buffer_gobble_aux:Nw \use:n
    \or:   \__buffer_gobble_aux:Nw \use_none:n 
    \or:   \__buffer_gobble_aux:Nw \use_none:nn 
    \or:   \__buffer_gobble_aux:Nw \use_none:nnn 
    \or:   \__buffer_gobble_aux:Nw \use_none:nnnn 
    \or:   \__buffer_gobble_aux:Nw \use_none:nnnnn 
    \or:   \__buffer_gobble_aux:Nw \use_none:nnnnnn 
    \or:   \__buffer_gobble_aux:Nw \use_none:nnnnnnn 
    \else: \__buffer_gobble_aux:Nw \use_none:nnnnnnnn 
    \fi:
  }
\cs_new:Npn \__buffer_gobble_aux:Nw #1 #2 \fi:
  {
    \fi:
    \exp_after:wN \__buffer_gobble_aux:w #1
  }
\cs_new:Npn \__buffer_gobble_aux:w #1 \__buffer_nothing: #2 \s_stop
  { \__buffer_unexpanded:w {#1} }
\cs_new:Npn \__buffer_int_to_slot:n #1
  {
    \if_case:w \exp_args:Nf \tl_count:n { \int_to_Hex:n {#1} } \exp_stop_f:
    \or: 000 \or: 00 \or: 0 \fi:
    \int_to_Hex:n {#1}
  }
\cs_new:Npn \__buffer_str_get_right:n #1 % cannot be empty 
  { \__buffer_str_get_right_aux:NN #1 \s_stop \q_stop }
\cs_new:Npn \__buffer_str_get_right_aux:NN #1#2
  {
    \use_none_delimit_by_s_stop:w #2 
    \__buffer_str_get_right_aux:Nw #1 \s_stop 
    \__buffer_str_get_right_aux:NN #2
  }
\cs_new:Npn \__buffer_str_get_right_aux:Nw #1 \s_stop 
  { \exp_not:N #1 \use_none_delimit_by_q_stop:w } 
\cs_new:Npn \__buffer_str_pop_right:n #1 % cannot be empty
  { \__buffer_str_pop_right_aux:nNN {} #1 \s_stop \q_stop }
\cs_new:Npn \__buffer_str_pop_right_aux:nNN #1#2#3
  {
    \use_none_delimit_by_s_stop:w #3
    \__buffer_str_pop_right_aux:nw {#1} \s_stop
    \__buffer_str_pop_right_aux:nNN { #1#2 } #3
  }
\cs_new:Npn \__buffer_str_pop_right_aux:nw #1 \s_stop
  { \__buffer_unexpanded:w \exp_after:wN {#1} \use_none_delimit_by_q_stop:w }
\cs_new:Npn \__buffer_str_get_left:w #1#2 \s_stop {#1}
\cs_new:Npn \__buffer_str_pop_left:w #1#2 \s_stop {#2}


%region type buffer
\int_new:N \l__buffer_csseq_length_int 
\bool_new:N \l__buffer_type_bool
\keys_define:nn { buffer } { type .meta:nn = { buffer/type } {#1} }
\keys_define:nn { buffer/type }
  {
    __register .code:n = \tl_map_inline:nn {#1} { \buffer_register_char:n { `##1 } } ,
    __enable   .code:n = \tl_map_inline:nn {#1} { \buffer_enable_char:n  { `##1 } } ,
    __disable  .code:n = \tl_map_inline:nn {#1} { \buffer_disable_char:n { `##1 } } ,

    true .code:n = 
      \bool_set_true:N \l__buffer_type_bool
      \bool_set_true:N \l__buffer_prepare_rescan_bool ,
    false .code:n = \bool_set_true:N \l__buffer_type_bool ,
  }


% 对于一对分隔符，需要注册首字符，以便能被识别
\cs_new_protected:Npn \buffer_register_char:n #1 % integer expr 
  {
    \bool_if_exist:cF { l__buffer_csseq_enable/ \int_eval:n {#1} _bool }
      {
        \bool_set_eq:cN { l__buffer_csseq_enable/ \int_eval:n {#1} _bool } \c_false_bool
        % 最大长度 0-9, 包括其本身
        \exp_after:wN \__buffer_let_int:w 
          \cs:w l__buffer_csseq_enable/ \int_eval:n {#1} _int \cs_end: \c_zero_int
      }
  }
\cs_new_protected:Npn \buffer_register_char:nn #1#2 % start, end 
  { \int_step_function:nnnN {#1} { 1 } {#2} \buffer_register_char:n }
% 注册 0-255 的字符 
\cs_new_protected:Npn \buffer_disable_char:n #1
  {
    \bool_if_exist:cTF { l__buffer_csseq_enable/ \int_eval:n {#1} _bool }
      { \bool_set_false:c { l__buffer_csseq_enable/ \int_eval:n {#1} _bool } }
      { \msg_warning:nnx { buffer } { char-enable-unknown } { \__buffer_int_to_slot:n {#1} } }
  }
\cs_new_protected:Npn \buffer_enable_char:n #1
  {
    \bool_if_exist:cTF { l__buffer_csseq_enable/ \int_eval:n {#1} _bool }
      { \bool_set_true:c { l__buffer_csseq_enable/ \int_eval:n {#1} _bool } }
      { \msg_warning:nnx { buffer } { char-enable-unknown } { \__buffer_int_to_slot:n {#1} } }
  }
\cs_new:Npn \buffer_char_if_enable:NTF #1
  {
    \bool_lazy_and:nnTF
      { \bool_if_exist_p:c { l__buffer_csseq_enable/ \int_value:w `#1 _bool } }
      { \bool_if_p:c { l__buffer_csseq_enable/ \int_value:w `#1 _bool } }
  }
\cs_new:Npn \buffer_char_if_enable:nTF #1
  {
    \bool_lazy_and:nnTF
      { \bool_if_exist_p:c { l__buffer_csseq_enable/ \int_eval:n {#1} _bool } }
      { \bool_if_p:c { l__buffer_csseq_enable/ \int_eval:n {#1} _bool } }
  }
\buffer_register_char:nn { 0 } { 255 }

\cs_new:Npn \buffer_if_csseq:nTF #1
  {
    % 不使用 \cs_if_exist:cTF, 它会检查特殊情况，这里是不必的
    \if_cs_exist:w __buffer_csseq/~ #1 :w \cs_end:
      \exp_after:wN \use_i:nn
    \else:
      \exp_after:wN \use_ii:nn
    \fi:
  }
\cs_new:Npn \buffer_set_csseq:npn #1 { \cs_set_nopar:cpn { __buffer_csseq/~ #1 :w } }
\cs_new:Npn \buffer_set_csseq:npx #1 { \cs_set_nopar:cpx { __buffer_csseq/~ #1 :w } }
\cs_new:Npn \buffer_csseq_use:n #1 
  {

  }

\cs_new:Npn \__buffer_char_if_catcode:NnTF #1#2
  {
    \if_int_compare:w \int_value:w 
      \intarray_item:cn { g__buffer_saved_cctab@ \l__buffer_id_tl _intarray } 
        { `#1 + 1 } = \int_eval:w #2 \scan_stop: \exp_stop_f:
      \exp_after:wN \use_i:nn
    \else: \exp_after:wN \use_ii:nn
    \fi:
  }
\cs_new_protected:Npn \__buffer_feed_line:n #1
  {
    \__buffer_feed_line_loop:w #1 
      \__buffer_nothing: \__buffer_nothing: \__buffer_nothing:
      \__buffer_nothing: \__buffer_nothing: \__buffer_nothing:
      \__buffer_nothing: \__buffer_nothing: \__buffer_nothing: \q__buffer_stop
  }
\cs_new:Npn \__buffer_feed_line_loop:w #1
  {
    \tl_if_empty:oTF {#1}
      { \__buffer_feed_line_loop:w }
      {
        \tl_if_single_token:nTF {#1}
          {
            \token_if_cs:NTF #1
              {
                \token_case_meaning:NnF #1
                  {
                    \q__buffer_stop { \__buffer_end_current_line: }
                    \__buffer_feed_line_stop:w { \__buffer_feed_line_stop:w }
                    \para_end: 
                      { \__buffer_add_to_output_normal:n { \par } \__buffer_feed_line_loop:w }
                  }
                  { \__buffer_grab_expand_to_output:Nn #1 }
              }
              {
                \buffer_char_if_enable:NTF #1
                  { \__buffer_grab_csseq_char:Nw #1 }
                  {
                    \__buffer_char_if_catcode:NnTF #1 \c_zero_int
                      { \__buffer_grab_cs:Nw {#1} } % #1 may be a space 
                      { 
                        \__buffer_add_to_output_char:N #1 
                        \__buffer_feed_line_loop:w 
                      }
                  }
              }
          }
          { 
            \__buffer_add_to_output_normal:n {#1} 
            \__buffer_feed_line_loop:w 
          }
      }
  }
\cs_new:Npn \__buffer_end_current_line: 
  {
    
  }
\cs_new:Npn \__buffer_grab_expand_to_output:Nn #1#2
  { 
    \exp_args:No \__buffer_add_to_output_normal:n { #1 {#2} } 
    \__buffer_feed_line_loop:w 
  }
\cs_new:Npn \__buffer_grab_csseq_char:Nw #1
  {
    \int_zero:N \l__buffer_tmpa_int
    \int_set:Nn \l__buffer_csseq_length_int 
      { \use:c { l__buffer_csseq_enable/ \int_value:w `#1 _int } }
    \int_compare:nNnTF \l__buffer_csseq_length_int > 0
      { 
        \__buffer_toks:w 0 { #1 }
        \peek_after:Nw \__buffer_grab_csseq_finish_or_not: 
      }
      { \__buffer_feed_line_loop:w #1 }
  }
\cs_new:Npn \__buffer_grab_csseq_finish_or_not:
  {
    % 仅 other 被允许作为 csseq 
    \token_if_other:NTF \l_peek_token
      { \__buffer_grab_csseq_next:n }
      { \__buffer_grab_csseq_finish: }
  }
\cs_new:Npn \__buffer_grab_csseq_next:n #1
  {
    \int_incr:N \l__buffer_tmpa_int
    \__buffer_toks:w 0 \exp_after:wN { \tex_the:D \__buffer_toks:w 0 \exp_stop_f: #1 }
    \int_compare:nNnTF \l__buffer_tmpa_int < \l__buffer_csseq_length_int
      { \peek_after:Nw \__buffer_grab_csseq_finish_or_not: }
      { \__buffer_grab_csseq_finish: }
  }
\cs_new:Npn \__buffer_grab_csseq_finish:
  {
    \exp_args:No \__buffer_validate_csseq:nn
      { \tex_the:D \__buffer_toks:w 0 \exp_stop_f: }
      {}
  }
\cs_new:Npn \__buffer_validate_csseq:nn #1#2
  {
    \__buffer_if_empty:nTF {#1}
      { \__buffer_feed_line_loop:w #2 }
      {
        \buffer_if_csseq:nTF {#1}
          { \__buffer_csseq_exec:n {#1} \__buffer_feed_line_loop:w #2 }
          {
            \exp_args:Nee \__buffer_validate_csseq:nn 
              { \__buffer_str_pop_right:n {#1} }
              { #2 \__buffer_str_get_right:n {#1} }
          }
      }
  }
\cs_new:Npn \__buffer_csseq_exec:n
  { \buffer_csseq_use:n }
\cs_new:Npn \__buffer_grab_cs:Nw #1
  {
    \__buffer_toks:w 0 { {#1} }
    \peek_after:Nw \__buffer_grab_cs_next_first:
  }
\cs_new:Npn \__buffer_grab_cs_next_first:
  {
    \token_if_other:NTF \l_peek_token 
      { \__buffer_grab_cs_next_first:n }
      { 
        \exp_after:wN \__buffer_add_to_output_char:N \tex_the:D \__buffer_toks:w \c_zero_int
        \__buffer_feed_line_loop:w 
      }
  }
\cs_new:Npn \__buffer_grab_cs_next_first:n #1
  {
    \__buffer_toks:w 0 \exp_after:wN { \tex_the:D \__buffer_toks:w \c_zero_int #1 }
    \__buffer_char_if_catcode:NnTF #1 { 11 }
      { \peek_after:Nw \__buffer_grab_cs_next: }
      {
        \exp_after:wN \__buffer_add_to_output_cs_char:Nw \tex_the:D \__buffer_toks:w \c_zero_int \s_stop
        \__buffer_feed_line_loop:w 
      }
  }
\cs_new:Npn \__buffer_grab_cs_next:
  {
    \token_if_other:NTF \l_peek_token
      { \__buffer_grab_cs_next:n }
      {
        \exp_after:wN \__buffer_add_to_output_cs_word:Nw \tex_the:D \__buffer_toks:w \c_zero_int \s_stop
        \__buffer_feed_line_loop:w 
      }
  }
\cs_new:Npn \__buffer_grab_cs_next:n #1
  {
    \__buffer_char_if_catcode:NnTF #1 { 11 }
      {
        \__buffer_toks:w 0 \exp_after:wN { \tex_the:D \__buffer_toks:w 0 \exp_stop_f: #1 }
        \peek_after:Nw \__buffer_grab_cs_next:
      }
      {
        \exp_after:wN \__buffer_add_to_output_cs_word:Nw \tex_the:D \__buffer_toks:w \c_zero_int \s_stop
        \__buffer_feed_line_loop:w #1
      }
  }
\cs_new_protected:Npn \__buffer_add_to_output_normal:n #1 
  {
    \tl_set:Nn \l__buffer_curr_type_tl { 0 }
    \__buffer_add_to_output:nn { Text } {#1}
  }
\cs_new_protected:Npn \__buffer_add_to_output_char:N #1
  { % #1 may be \char`\\
    \tl_set:Nn \l__buffer_curr_type_tl { 0 }
    \__buffer_add_to_output:nn { Text } {#1}
  }
\cs_new_protected:Npn \__buffer_add_to_output_cs_char:Nw #1#2 \s_stop
  { \__buffer_add_to_output_texcs:Nn {#1} {#2} }
\cs_new_protected:Npn \__buffer_add_to_output_cs_word:Nw #1#2 \s_stop
  { \__buffer_add_to_output_texcs:Nn #1 {#2} }
\cs_new:Npn \__buffer_add_to_output_texcs:Nn #1#2 % escapechar, cs name 
  {
    \tl_set:Nn \l__buffer_curr_type_tl { }
    \__buffer_add_to_output:nn { Texcs } { }
  }
\tl_new:N \l__buffer_curr_type_tl 
\tl_new:N \l__buffer_curr_output_tl
\int_new:N \l__buffer_curr_output_int 
\__buffer_let_int:w \c__buffer_NULL_int          0 \exp_stop_f:
\__buffer_let_int:w \c__buffer_Keyword_int       1 \exp_stop_f:
\__buffer_let_int:w \c__buffer_Texcs_int         2 \exp_stop_f:
\__buffer_let_int:w \c__buffer_Letter_int        3 \exp_stop_f:
\__buffer_let_int:w \c__buffer_Digit_int         4 \exp_stop_f:
\__buffer_let_int:w \c__buffer_Text_int          5 \exp_stop_f:
\__buffer_let_int:w \c__buffer_Punctuation_int   6 \exp_stop_f:
\__buffer_let_int:w \c__buffer_Brace_int         7 \exp_stop_f:
\__buffer_let_int:w \c__buffer_Escape_int        8 \exp_stop_f:
\__buffer_let_int:w \c__buffer_Comment_int       9 \exp_stop_f:
\__buffer_let_int:w \c__buffer_PairStart_int    10 \exp_stop_f:
\__buffer_let_int:w \c__buffer_PairStop_int     11 \exp_stop_f:
\__buffer_let_int:w \c__buffer_CommentStart_int 12 \exp_stop_f:
\__buffer_let_int:w \c__buffer_CommentStop_int  13 \exp_stop_f:
\__buffer_let_int:w \c__buffer_Newline_int      14 \exp_stop_f:
\cs_new_protected:Npn \__buffer_add_to_output:nn #1#2
  {

  }
\cs_new_protected:Npn \__buffer_feed_line_stop:w 
  {

  }

\msg_new:nnn { buffer } { char-enable-unknown } 
  { The~char~`U+#1'~is~unregistered,~cannot~be~enabled~or~disabled. }
%endregion


\cs_new_protected_nopar:Npn \BufferKeyword #1#2#3 % type, , word
  {

  }
\cs_new_protected_nopar:Npn \BufferTexcs #1#2#3 % type, , cs
  {

  }
\cs_new_protected_nopar:Npn \BufferLetter #1#2 % type, word
  {

  }
\cs_new_protected_nopar:Npn \BufferDigit #1#2 % type, digit
  {

  }
\cs_new_protected_nopar:Npn \BufferText #1#2 % type, text 
  {

  }
\cs_new_protected_nopar:Npn \BufferPunctuation #1#2 % type, punct (cs: \%, \;)
  {

  }
\cs_new_protected_nopar:Npn \BufferBrace #1#2#3 % type, count, brace (cs: \{,\})
  {

  }
\cs_new_protected_nopar:Npn \BufferEscape #1#2 % type, escaped
  {

  }
\cs_new_protected_nopar:Npn \BufferComment #1#2 % type, comment
  {

  }
\cs_new_protected_nopar:Npn \BufferPairStart #1 % type 
  {

  }
\cs_new_protected_nopar:Npn \BufferPairStop #1 % type 
  {

  }
\cs_new_protected_nopar:Npn \BufferCommentStart #1 % type 
  {

  }
\cs_new_protected_nopar:Npn \BufferCommentStop #1 % type 
  {

  }
\cs_new_protected_nopar:Npn \BufferNewline
  {

  }

\cs_new_protected_nopar:Npn \BufferEndofline
  {

  }